#ifndef SHARED_MEMORY_INL
#define SHARED_MEMORY_INL

namespace shared_memory
{
template<class T>
T *SharedMemory<T>::data()
{
    T *object = static_cast<T *>(m_memory);
    return object;
}
template<class T>
SharedMemory<T>::SharedMemory()
    : m_error(NoError), m_size(0)
{
    setKey(1);
}
template<class T>
SharedMemory<T>::SharedMemory(int key)
    : m_error(NoError), m_size(0)
{
    setKey(key);
}
template<class T>
SharedMemory<T>::SharedMemory(const std::string &path)
    : m_error(NoError), m_size(0)
{
    setKey(path);
}
template<class T>
SharedMemory<T>::~SharedMemory()
{
    setKey(-1);
}
template<class T>
void SharedMemory<T>::setKey(int key)
{
    m_key = key;
}
template<class T>
void SharedMemory<T>::setKey(const std::string &path)
{
    setKey(ftok(path.c_str(), 's'));
}
template<class T>
bool SharedMemory<T>::create(int size, AccessMode mode)
{
    if (m_key < 0)
    {
        return false;
    }
    m_shm_id = shmget(m_key, size, 0666 | IPC_CREAT | IPC_EXCL);
    if (-1 == m_shm_id)
    {
        switch (errno)
        {
            case EINVAL:
                m_error = InvalidSize;
                break;
            case EEXIST:
                m_error = AlreadyExists;
                break;
            default:
                break;
        }
        return false;
    }
    return attach(mode);
}
template<class T>
int SharedMemory<T>::size() const
{
    return m_size;
}
template<class T>
bool SharedMemory<T>::attach(AccessMode mode)
{
    // grab the shared memory segment id
    int id = shmget(m_key, 0, (mode == ReadOnly ? 0400 : 0600));
    if (-1 == id)
    {
        m_errorString = "attach (shmget) error";
        return false;
    }
    // grab the memory
    m_memory = shmat(id, nullptr, (mode == ReadOnly ? SHM_RDONLY : 0));
    if ((void *) -1 == m_memory)
    {
        m_memory = nullptr;
        m_errorString = "attach (shmat) error";
        return false;
    }
    // grab the size
    shmid_ds shmid_ds{};
    if (!shmctl(id, IPC_STAT, &shmid_ds))
    {
        m_size = (int) shmid_ds.shm_segsz;
    }
    else
    {
        m_errorString = "attach (shmctl) error";
        return false;
    }
    return true;
}
template<class T>
bool SharedMemory<T>::isAttached() const
{
    return (nullptr != m_memory);
}
template<class T>
bool SharedMemory<T>::detach()
{
    if (!isAttached() || m_key < 0)
    {
        return false;
    }
    if (-1 == shmdt(m_memory))
    {
        switch (errno)
        {
            case EINVAL:
                m_errorString = "detach(shmdt) error";
                m_error = NotFound;
                break;
            default:
                m_error = UnknownError;
                break;
        }
    }
    m_memory = nullptr;
    m_size = 0;
    // Get the number of current attachments
    int id = shmget(m_key, 0, 0400);
    shmid_ds shmid_ds{};
    if (0 != shmctl(id, IPC_STAT, &shmid_ds))
    {
        switch (errno)
        {
            case EINVAL:
                return true;
            default:
                return false;
        }
    }
    // If there are no attachments then remove it.
    if (shmid_ds.shm_nattch == 0)
    {
        // mark for removal
        if (-1 == shmctl(id, IPC_RMID, &shmid_ds))
        {
            m_errorString = "SharedMemory::remove";
            switch (errno)
            {
                case EINVAL:
                    return true;
                default:
                    return false;
            }
        }
    }
    return true;
}
template<class T>
SharedMemoryError SharedMemory<T>::error() const
{
    return m_error;
}
template<class T>
std::string SharedMemory<T>::errorString() const
{
    return m_errorString;
}
}


#endif //SHARED_MEMORY_INL
